import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.locks.LockSupport;

// java --enable-preview PrimeC
public class PrimeC5 {
	static final Thread.Builder.OfVirtual threadFactory = Thread.ofVirtual();

	static class TransferQueue<E> {
		static final class QNode implements ForkJoinPool.ManagedBlocker {
			private static final VarHandle QITEM;
			private static final VarHandle QNEXT;
			private static final VarHandle QWAITER;

			static {
				try {
					MethodHandles.Lookup l = MethodHandles.lookup();
					QITEM = l.findVarHandle(QNode.class, "item", Object.class);
					QNEXT = l.findVarHandle(QNode.class, "next", QNode.class);
					QWAITER = l.findVarHandle(QNode.class, "waiter", Thread.class);
				} catch (ReflectiveOperationException e) {
					throw new ExceptionInInitializerError(e);
				}
			}

			volatile QNode next;          // next node in queue
			volatile Object item;         // CAS'ed to or from null
			volatile Thread waiter;       // to control park/unpark
			final boolean isData;

			QNode(Object item, boolean isData) {
				this.item = item;
				this.isData = isData;
			}

			boolean casNext(QNode val) {
				return next == null && QNEXT.compareAndSet(this, (QNode)null, val);
			}

			boolean casItem(Object cmp, Object val) {
				return item == cmp && QITEM.compareAndSet(this, cmp, val);
			}

			/**
			 * Returns true if this node is known to be off the queue
			 * because its next pointer has been forgotten due to
			 * an advanceHead operation.
			 */
			boolean isOffList() {
				return next == this;
			}

			void forgetWaiter() {
				QWAITER.setOpaque(this, null);
			}

			boolean isFulfilled() {
				Object x;
				return isData == ((x = item) == null) || x == this;
			}

			@Override
			public boolean isReleasable() {
				Object x;
				return isData == ((x = item) == null) || x == this || Thread.currentThread().isInterrupted();
			}

			@Override
			public boolean block() {
				while (!isReleasable())
					LockSupport.park();
				return true;
			}
		}

		private static final VarHandle QHEAD;
		private static final VarHandle QTAIL;

		static {
			try {
				MethodHandles.Lookup l = MethodHandles.lookup();
				QHEAD = l.findVarHandle(TransferQueue.class, "head", QNode.class);
				QTAIL = l.findVarHandle(TransferQueue.class, "tail", QNode.class);
			} catch (ReflectiveOperationException e) {
				throw new ExceptionInInitializerError(e);
			}
		}

		transient volatile QNode head;
		transient volatile QNode tail;

		TransferQueue() {
			QNode h = new QNode(null, false); // initialize to dummy node.
			head = h;
			tail = h;
		}

		/**
		 * Tries to cas nh as new head; if successful, unlink
		 * old head's next node to avoid garbage retention.
		 */
		void advanceHead(QNode h, QNode nh) {
			if (h == head && QHEAD.compareAndSet(this, h, nh))
				h.next = h; // forget old next
		}

		/**
		 * Tries to cas nt as new tail.
		 */
		void advanceTail(QNode t, QNode nt) {
			if (tail == t)
				QTAIL.compareAndSet(this, t, nt);
		}

		/**
		 * @see java.util.concurrent.SynchronousQueue.TransferQueue#transfer
		 */
		@SuppressWarnings({"unchecked", "JavadocReference"})
		E transfer(E e) {
			QNode s = null;                  // constructed/reused as needed
			for (boolean isData = e != null; ; ) {
				QNode h = head, t = tail, m, tn;         // m is node to fulfill
				if (h == t || t.isData == isData) {      // empty or same-mode
					//noinspection StatementWithEmptyBody
					if (t != tail)                       // inconsistent
						;
					else if ((tn = t.next) != null)      // lagging tail
						advanceTail(t, tn);
					else if (t.casNext(s != null ? s : (s = new QNode(e, isData)))) {
						advanceTail(t, s);
						Thread w = Thread.currentThread();
						int stat = -1; // same idea as TransferStack
						Object item;
						while ((item = s.item) == e) {
							if ((item = s.item) != e)
								break;                   // recheck
							if (stat <= 0) {
								if (t.next == s) {
									if (stat < 0 && t.isFulfilled()) {
										stat = 0;        // yield once if first
										Thread.yield();
									} else {
										stat = 1;
										s.waiter = w;
									}
								}
							} else {
								LockSupport.setCurrentBlocker(this);
								try {
									ForkJoinPool.managedBlock(s);
								} catch (InterruptedException cannotHappen) {
								}
								LockSupport.setCurrentBlocker(null);
							}
						}
						if (stat == 1)
							s.forgetWaiter();
						if (!s.isOffList()) {            // not already unlinked
							advanceHead(t, s);           // unlink if head
							if (item != null)            // and forget fields
								s.item = s;
						}
						return item != null ? (E)item : e;
					}

				} else if ((m = h.next) != null && t == tail && h == head) {
					Object x = m.item;
					boolean fulfilled = ((isData == (x == null)) && x != m && m.casItem(x, e));
					advanceHead(h, m);                   // (help) dequeue
					if (fulfilled) {
						Thread waiter;
						if ((waiter = m.waiter) != null)
							LockSupport.unpark(waiter);
						return x != null ? (E)x : e;
					}
				}
			}
		}
	}

	static final class IntChan extends TransferQueue<Integer> {
		void put(int v) {
			transfer(v);
		}

		int take() {
			return transfer(null);
		}
	}

	static void go(Runnable r) {
		threadFactory.start(r);
	}

	static void generate(IntChan ch) {
		try {
			//noinspection InfiniteLoopStatement
			for (int i = 2; ; i++)
				ch.put(i);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	static void filter(IntChan in, IntChan out, int prime) {
		try {
			//noinspection InfiniteLoopStatement
			for (; ; ) {
				var i = in.take();
				if (i % prime != 0)
					out.put(i);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		var count = args.length > 0 ? Integer.parseInt(args[0]) : 10000;
		go(() -> {
			var ch = new IntChan();
			var ch0 = ch;
			go(() -> generate(ch0));
			try {
				for (int i = 0; ; ) {
					var prime = ch.take();
					if (++i == count) {
						System.out.println(prime);
						System.exit(0);
					}
					var ch1 = ch;
					var ch2 = new IntChan();
					go(() -> filter(ch1, ch2, prime));
					ch = ch2;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		});
		Thread.sleep(1_000_000L);
	}
}
